import json
import time

from flask import current_app, request
from flask_restx import abort, reqparse
from sqlalchemy import and_, or_

from app.common.range_manager import RangeManager
from app.models import coursewareModel, rangeModel, switchModel
from tasks.tasks import update_courseware_info, delete_redis_info
from tools import db
from tools import redis_conn as redis_store
from utils.utils import base_detail

from app.api.v1.webhook.webhook_manager import WebHookManager
from template import l2_network_tpl, l3_network_tpl, vm_tpl, basic_tpl, router_tpl, eip_tpl, security_group_tpl
from .base_view import OrganizerBase
from tools.basic_tools import Tools
from app.api.v1.organizer.rangeEnv import RangeStackTemplate


class CourseWare(OrganizerBase):
    def get_courseware(self, id=None, name=None):
        courseware_info = None
        if id:
            courseware_info = coursewareModel.Courseware.query.filter_by(id=id).first()
        elif name:
            courseware_info = coursewareModel.Courseware.query.filter_by(
                name=name
            ).first()
        return courseware_info


class CourseWareManage(CourseWare):
    def get(self, id=None):
        courseware_info = self.get_courseware(id=id)
        if not courseware_info:
            abort(400, message="课件不存在")
        return {"code": 200, "data": courseware_info.to_dict()}

    def post(self):
        parser = reqparse.RequestParser(bundle_errors=True)
        parser.add_argument("name", help="课件名称不能为空", required=True)
        parser.add_argument("type", help="选择课件类型", required=True)
        parser.add_argument("video")
        parser.add_argument("pdf")
        parser.add_argument("range_uuid")
        parser.add_argument("pdfname")
        parser.add_argument("videoname")
        args = parser.parse_args()
        if not all([args["name"], args["type"]]):
            abort(400, message="参数错误")

        # 取值范围设定
        type_scope = ("1", "2", "3")
        if args["type"] not in type_scope:
            abort(400, message="type格式错误")

        # type '1'：文本类；'2'：视频类；'3'：虚拟机类
        if args["type"] == "1":
            # 上传pdf
            if not args["pdf"]:
                abort(400, message="请上传pdf")

        if args["type"] == "2":
            # 上传video
            if not args["video"]:
                abort(400, message="请上传video")

        if args["range_uuid"]:
            range_id = rangeModel.Range.query.filter_by(id=args["range_uuid"]).all()
            if not range_id:
                abort(400, message="靶场环境不存在")

        courseware_info = self.get_courseware(name=args["name"])
        if courseware_info:
            abort(400, message="课件名已存在，请更换！")

        with db.auto_commit():
            obj = coursewareModel.Courseware(
                name=args["name"],
                type=args["type"],
                user_id=self.user.id,
                pdfPath=args.get("pdf", None),
                videoPath=args.get("video", None),
                range_uuid=args.get("range_uuid", None),
                pdf_filename=args.get("pdfname", ""),
                video_filename=args.get("videoname", "")
            )
            coursewareModel.db.session.add(obj)
            coursewareModel.db.session.commit()
            current_app.logger.info(
                f"{self.user.username}---action--add--courseware:" + args["name"]
            )
            return {"code": 200, "data": obj.to_dict()}

    def put(self, id=None):
        if not id:
            abort(400, message="参数错误")

        args = request.json
        # 根据id获取用户信息
        courseware_info = self.get_courseware(id=id)
        if not courseware_info:
            abort(400, message="该课件不存在")

        args.update({"pdfPath": args.pop("pdf", None)})
        args.update({"videoPath": args.pop("video", None)})
        args.update({"pdf_filename": args.pop("pdfname", None)})
        args.update({"video_filename": args.pop("videoname", None)})


        # 判断课件名
        courseware_info_by_name = self.get_courseware(name=args["name"])
        if courseware_info_by_name and courseware_info_by_name.id != id:
            abort(400, message="课件名已存在，请更换！")

        with db.auto_commit():
            coursewareModel.Courseware.query.filter_by(id=id).update(args)
            current_app.logger.info(f"---action--put--courseware id:{str(id)}")
            return {"code": 200, "data": courseware_info.to_dict()}

    def delete(self, id=None):
        with db.auto_commit():
            courseware_info = self.get_courseware(id=id)
            if not courseware_info:
                abort(400, message="courseware not exist")
            # 关联章节的课件无法被删除  change: 2022.1.26
            if courseware_info.api_chapter:
                abort(400, message="该课件已有关联课程，不允许被删除")
            coursewareModel.db.session.delete(courseware_info)
            return {"code": 200, "data": "删除成功"}


# 课件列表页 我的课件接口
class CourseWareList(CourseWare):
    def get(self):
        params = request.args
        page = params.get("page", 1)
        page_size = params.get("pageSize", 10)
        system = params.get("system")
        keyword = params.get("keyword")
        can_add = params.get("canAdd", "0") == "1"
        try:
            page = int(page)
            page_size = int(page_size)
        except Exception as e:  # noqa
            page = 1
            page_size = 10

        if page_size >= 50:
            abort(400, message="每页数量不能超过50")

        if system not in ["0", "1", "2"]:
            abort(400, message="system参数范围为（'0', '1', '2'）")

        try:
            filters = []
            if keyword:
                # 添加关键词的检索选项
                filters.append(coursewareModel.Courseware.name.contains(keyword))

            if can_add:
                # 过滤虚拟机类型且没有range_uuid的课件
                filters.append(
                    or_(
                        and_(coursewareModel.Courseware.type != "3"),
                        and_(
                            coursewareModel.Courseware.range_uuid.isnot(None),
                            coursewareModel.Courseware.type == "3",
                        ),
                    )
                )

            if system == "0":
                paginate = (
                    coursewareModel.Courseware.query.filter(*filters)
                    .filter_by(isSystem=0, user_id=self.user.id)
                    .order_by(coursewareModel.Courseware.id.desc())
                    .paginate(page, page_size, error_out=False)
                )
            elif system == "1":
                paginate = (
                    coursewareModel.Courseware.query.filter(*filters)
                    .filter_by(isSystem=1)
                    .order_by(coursewareModel.Courseware.id.desc())
                    .paginate(page, page_size, error_out=False)
                )
            else:
                task_filter = {
                    or_(
                        and_(coursewareModel.Courseware.isSystem == 1),
                        and_(
                            coursewareModel.Courseware.isSystem == 0,
                            coursewareModel.Courseware.user_id == self.user.id,
                        ),
                    )
                }
                paginate = (
                    coursewareModel.Courseware.query.filter(*filters)
                    .filter(*task_filter)
                    .order_by(coursewareModel.Courseware.id.desc())
                    .paginate(page, page_size, error_out=False)
                )

            info = {"current_page": page, "data": []}
            if len(paginate.items) > 0:
                for courseware in paginate.items:
                    is_collected = False
                    is_associated = False
                    if courseware in self.user.collection_courseware:
                        is_collected = True
                    if courseware.api_chapter:
                        is_associated = True
                    courseware_info = courseware.to_dict()
                    courseware_info["is_collected"] = is_collected
                    courseware_info["is_associated"] = is_associated
                    info["data"].append(courseware_info)
            info = base_detail(info, paginate)
            return {"code": 200, "data": info}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="参数错误")


class CoursewareCollect(CourseWare):
    def get(self):
        # sourcery skip: dict-literal, extract-method, merge-dict-assign
        params = request.args
        page = params.get("page", 1)
        page_size = params.get("pageSize", 10)
        keyword = params.get("keyword")
        try:
            page = int(page)
            page_size = int(page_size)
        except Exception as e:  # noqa
            page = 1
            page_size = 10

        if page_size >= 50:
            abort(400, message="每页数量不能超过50")

        try:
            filters = []
            if keyword:
                # 添加关键词的检索选项
                filters.append(coursewareModel.Courseware.name.contains(keyword))

            paginate = (
                self.user.collection_courseware.filter(*filters)
                .order_by(coursewareModel.Courseware.id.desc())
                .paginate(page, page_size, error_out=False)
            )

            info = dict()  # noqa
            info["current_page"] = page
            info["data"] = []
            if len(paginate.items) > 0:
                for content in paginate.items:
                    info["data"].append(content.to_dict())
            info = base_detail(info, paginate)
            return {"code": 200, "data": info}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="参数错误")

    def post(self):
        """课件收藏"""
        user = self.user
        parser = reqparse.RequestParser()
        parser.add_argument("courseware_id", help="课件不能为空", required=True)
        parser.add_argument("action", help="参数错误", required=True)

        args = parser.parse_args()
        if not all([args["courseware_id"], args["action"]]):
            abort(400, message="参数错误")

        if args["action"] not in ["cancel_collect", "collect"]:
            abort(400, message="参数错误")

        with db.auto_commit():
            courseware = coursewareModel.Courseware.query.get(args["courseware_id"])
            if not courseware:
                abort(400, message="课件不存在")

            if args["action"] == "collect":
                if courseware not in user.collection_courseware:
                    user.collection_courseware.append(courseware)
            elif courseware in user.collection_courseware:
                user.collection_courseware.remove(courseware)
            return {"code": 200, "data": "操作成功"}


class CoursewareStatus(CourseWare):
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    @staticmethod
    def celery_task_courseware(args):
        from tasks.tasks import close_courseware, start_courseware, add_job_to_scheduler, revoke_task

        start_task_key = f'CoursewareTaskStart:{args["courseware_uuid"]}'
        close_task_key = f'CoursewareTaskClose:{args["courseware_uuid"]}'

        # 检查是否有已注册任务, 存在则取消
        revoke_task([start_task_key, close_task_key])

        start = int(args["env_start_time"]) - int(time.time())
        end = int(args["env_end_time"]) - int(time.time())

        if args["env_status"] == "1":
            if args["present_time"] >= int(args["env_start_time"]):
                add_job_to_scheduler(
                    task_id=start_task_key, 
                    func=start_courseware,
                    trigger="date",
                    args=[ctf_id],
                    **{"run_date": Tools.timeStamp_to_strftime(start_time), 
                    "func_name": "start_ctf"}
                    )
                start_task = start_courseware.apply_async([args], countdown=start)
                redis_store.set(start_task_key, start_task.task_id, ex=int(args["env_end_time"]) + 5)

            close_task = close_courseware.apply_async([args], countdown=end)
            redis_store.set(close_task_key, close_task.task_id, ex=int(args["env_end_time"]) + 5)

        elif args["env_status"] == "0":
            args.update({"env_start_time": "null", "env_end_time": "null"})
            delete_redis_info(args["chapter_uuid"])

        update_courseware_info(args)

    def post(self):  # sourcery skip: extract-method
        parser = reqparse.RequestParser()
        parser.add_argument(
            "chapter_uuid", type=str, help="Bad choice: {error_msg}", required=True
        )
        parser.add_argument(
            "courseware_uuid", type=str, help="Bad choice: {error_msg}", required=True
        )
        parser.add_argument(
            "range_uuid", type=str, help="Bad choice: {error_msg}", required=True
        )
        parser.add_argument(
            "env_status", choices=("0", "1"), help="Bad choice: {error_msg}", required=True
        )
        parser.add_argument("env_start_time", type=str, help="Bad choice: {error_msg}")
        parser.add_argument("env_end_time", type=str, help="Bad choice: {error_msg}")
        args = parser.parse_args()

        if (
                args["range_uuid"] is None
                or args["courseware_uuid"] is None
                or args["chapter_uuid"] is None
        ):
            abort(400, message="请添加拓扑后在开启环境")

        if args["env_status"] == "1":
            range_manager = RangeManager(
                range_uuid=args["range_uuid"], ignore_private=True
            )
            if range_manager.range_model.Range.is_draft:
                abort(400, message="场景未确认修改，请联系管理员或教师操作")

        present_time = int(time.time())
        if int(args["env_start_time"]) >= int(args["env_end_time"]):
            abort(400, message="结束时间应大于开始时间")

        if present_time >= int(args["env_end_time"]):
            abort(400, message="结束时间应大于当前时间")

        if not self.get_courseware(id=args["courseware_uuid"]):
            abort(400, message="课件不存在")
            
        template_obj = rangeModel.StackTemplate.query.filter_by(range_uuid=args.range_uuid, is_system="1").first()

        try:
            args["present_time"] = present_time
            self.celery_task_courseware(args)
            if args["env_status"] == "1":
                # 生成资源模板
                if template_obj:
                    abort(400, message="已存在场景")
                range_data = RangeStackTemplate._building_data(args.range_uuid)
                # 1. 二层网络
                range_model = rangeModel.RangeLinkModule.query.filter_by(range_uuid=args.range_uuid).first()
                # 构建json
                tpl_l2_pool_uuid = range_model.vxlan_pool_id
                l2_uuid = Tools.get_uuid4_no_line()
                zone_uuid = current_app.config["ZSTACK_CLOUD"]["zone_uuid"]
                pool_uuid = tpl_l2_pool_uuid

                l2_network = json.loads(l2_network_tpl.l2_network_str.format(
                    l2_uuid=l2_uuid, zone_uuid=zone_uuid, pool_uuid=pool_uuid))
                basic_json = basic_tpl.basic_json
                device_json = l2_network
                router_to_vm_list = []
                for vpc_uuid, value in range_data.items():
                    # 构建交换机
                    # 2.交换机
                    switch_obj = switchModel.Switchs.query.filter_by(range_uuid=args.range_uuid, subnet_id=vpc_uuid).first()
                    vpc_cidr = switch_obj.cidr
                    vpc_network = json.loads(l3_network_tpl.vpc_network_str.format(
                        vpc_uuid=vpc_uuid, l2_uuid=l2_uuid))
                    vpc_network_cidr = json.loads(l3_network_tpl.vpc_network_cidr.format(
                        vpc_uuid=vpc_uuid, vpc_cidr=vpc_cidr))
                    device_json = dict(device_json, **vpc_network, **vpc_network_cidr)
                    # 路由器
                    for router_num, (router_key, router_value) in enumerate(value.get("router_gw", {}).items()):
                        router_offer_uuid = current_app.config["ZSTACK_CLOUD"]["router_uuid"]
                        router_json, attach_router_json, router_to_vm = RangeStackTemplate._create_router_template(router_key, router_key, router_offer_uuid, router_num, vpc_uuid)
                        device_json = dict(device_json, **router_json, **attach_router_json)
                        router_to_vm_list.append(router_to_vm)
                    # 虚拟机
                    for vm_key, vm_value in value.get("vm", {}).items():
                        vm_json = RangeStackTemplate._create_vm_template(vpc_uuid, vm_key, vm_value, router_to_vm_list, start_vm=False)
                        device_json = dict(device_json, **vm_json)
                    # 安全组
                    basic_json["Resources"] = device_json
                resource_id = Tools.get_uuid4_no_line()
                self.load_cloud_object.add_stack_template(args.range_uuid, basic_json, resource_id, args.range_uuid, is_system=1)
            else:
                
                # 删除资源模板
                self.load_cloud_object.del_stack_template(
                    template_obj.stack_uuid
                )
                # 删除资源栈
                resource_obj = rangeModel.StackResource.query.filter_by(stack_uuid=template_obj.stack_uuid).first()
                if resource_obj:
                    self.load_cloud_object.del_resource_stack(resource_obj.resource_stack_uuid)
                rangeModel.StackTemplate.query.filter_by(range_uuid=args.range_uuid, is_system="1").delete()
                db.session.commit()
            return {"code": 200, "data": "操作成功"}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="开启/关闭 课件错误")
